// 《围棋》作者版权所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package runtime

// 此文件包含Go通道的实现。

// 不变量：
// c.sendq和c.recvq中至少有一个为空，
// 除了具有单个goroutine的无缓冲通道
// 使用select语句阻止发送和接收，
// 在这种情况下，c.sendq和c.recvq的长度仅受select语句的大小限制。
// 
// 对于缓冲通道，同样：
// c.qcount>0表示c.recvq为空。
// c.qcount<c.dataqsiz表示c.sendq为空。

import (
	"runtime/internal/atomic"
	"runtime/internal/math"
	"unsafe"
)

const (
	maxAlign  = 8
	hchanSize = unsafe.Sizeof(hchan{}) + uintptr(-int(unsafe.Sizeof(hchan{}))&(maxAlign-1))
	debugChan = false
)

type hchan struct {
	qcount   uint           // 队列中的总数据
	dataqsiz uint           // 循环队列的大小
	buf      unsafe.Pointer // 指向一个dataqsiz元素数组
	elemsize uint16
	closed   uint32
	elemtype *_type // 元素类型
	sendx    uint   // 发送索引
	recvx    uint   // 接收索引
	recvq    waitq  // 接收等待列表
	sendq    waitq  // 发送等待列表

	// 锁保护hchan中的所有字段，以及此通道上被阻止的sudogs中的几个
	// 字段。
	// 
	// 在持有此锁时不要更改另一个G的状态
	// （特别是，不要准备G），因为这会使
	// 因堆栈收缩而死锁。
	lock mutex
}

type waitq struct {
	first *sudog
	last  *sudog
}

// go:linkname reflect_makechan reflect.makechan 
func reflect_makechan(t *chantype, size int) *hchan {
	return makechan(t, size)
}

func makechan64(t *chantype, size int64) *hchan {
	if int64(int(size)) != size {
		panic(plainError("makechan: size out of range"))
	}

	return makechan(t, int(size))
}

func makechan(t *chantype, size int) *hchan {
	elem := t.elem

	// 编译器会检查此项，但要确保安全。
	if elem.size >= 1<<16 {
		throw("makechan: invalid channel element type")
	}
	if hchanSize%maxAlign != 0 || elem.align > maxAlign {
		throw("makechan: bad alignment")
	}

	mem, overflow := math.MulUintptr(elem.size, uintptr(size))
	if overflow || mem > maxAlloc-hchanSize || size < 0 {
		panic(plainError("makechan: size out of range"))
	}

	// 当存储在buf中的元素不包含指针时，Hchan不包含GC感兴趣的指针。
	// buf指向同一分配，elemtype是持久的。
	// SudoG从其所属线程引用，因此无法收集。
	// TODO（dvyukov，rlh）：重新考虑收集器何时可以移动分配的对象。
	var c *hchan
	switch {
	case mem == 0:
		// 队列或元素大小为零。
		c = (*hchan)(mallocgc(hchanSize, nil, true))
		// 竞赛检测器使用此位置进行同步。
		c.buf = c.raceaddr()
	case elem.ptrdata == 0:
		// 元素不包含指针。
		// 在一次调用中分配hchan和buf。
		c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
		c.buf = add(unsafe.Pointer(c), hchanSize)
	default:
		// 元素包含指针。
		c = new(hchan)
		c.buf = mallocgc(mem, elem, true)
	}

	c.elemsize = uint16(elem.size)
	c.elemtype = elem
	c.dataqsiz = uint(size)
	lockInit(&c.lock, lockRankHchan)

	if debugChan {
		print("makechan: chan=", c, "; elemsize=", elem.size, "; dataqsiz=", size, "\n")
	}
	return c
}

// chanbuf（c，i）是指向缓冲区中第i个插槽的指针。
func chanbuf(c *hchan, i uint) unsafe.Pointer {
	return add(c.buf, uintptr(i)*uintptr(c.elemsize))
}

// full报告c上的发送是否会阻塞（即通道已满）。
// 它使用可变状态的单个字大小的读取，因此尽管
// 答案立即为true，但在调用函数收到返回值时，正确的答案可能已经更改了
// 了。
func full(c *hchan) bool {
	// c.dataqsiz是不可变的（在创建通道后从未写入）
	// ，因此在通道操作过程中随时读取是安全的。
	if c.dataqsiz == 0 {
		// 假定指针读取是原子的。
		return c.recvq.first == nil
	}
	// 假设单元读取是原子的。
	return c.qcount == c.dataqsiz
}

// 编译代码中c<-x的入口点
// go:nosplit 
func chansend1(c *hchan, elem unsafe.Pointer) {
	chansend(c, elem, true, getcallerpc())
}

/*
 * generic single channel send/recv
 * If block is not nil,
 * then the protocol will not
 * sleep but return if it could
 * not complete.
 *
 * sleep can wake up with g.param == nil
 * when a channel involved in the sleep has
 * been closed.  it is easiest to loop and re-run
 * the operation; we'll see that it's now closed.
 */
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
	if c == nil {
		if !block {
			return false
		}
		gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)
		throw("unreachable")
	}

	if debugChan {
		print("chansend: chan=", c, "\n")
	}

	if raceenabled {
		racereadpc(c.raceaddr(), callerpc, funcPC(chansend))
	}

	// 快速路径：在不获取锁的情况下检查失败的非阻塞操作。
	// 
	// 在观察到通道未关闭后，我们观察到通道为
	// 未准备好发送。每个观察结果都是一个单词大小的读数
	// （第一个c.closed，第二个full（）。
	// 因为关闭的通道无法从“准备发送”转换为
	// “未准备发送”，即使通道在两次观测之间关闭，
	// 它们意味着两个通道之间的某个时刻，此时通道尚未关闭
	// 且未准备发送。我们的行为就好像我们当时观察到了频道，
	// 并报告发送无法继续。
	// 
	// 如果在此处对读取进行重新排序，则没有问题：如果我们观察到通道未准备好发送，然后观察到它未关闭，则表明
	// 通道在第一次观察期间未关闭。然而，这里没有任何东西能保证进步。我们依靠
	// chanrecv（）和closechan（）中锁释放的副作用来更新此线程的c.closed和full（）视图。
	if !block && c.closed == 0 && full(c) {
		return false
	}

	var t0 int64
	if blockprofilerate > 0 {
		t0 = cputicks()
	}

	lock(&c.lock)

	if c.closed != 0 {
		unlock(&c.lock)
		panic(plainError("send on closed channel"))
	}

	if sg := c.recvq.dequeue(); sg != nil {
		// 发现一个正在等待的接收器。我们绕过通道缓冲区（如果有），将要发送的值
		// 直接传递给接收器。
		send(c, sg, ep, func() { unlock(&c.lock) }, 3)
		return true
	}

	if c.qcount < c.dataqsiz {
		// 通道缓冲区中有可用空间。使要发送的元素排队。
		qp := chanbuf(c, c.sendx)
		if raceenabled {
			racenotify(c, c.sendx, nil)
		}
		typedmemmove(c.elemtype, qp, ep)
		c.sendx++
		if c.sendx == c.dataqsiz {
			c.sendx = 0
		}
		c.qcount++
		unlock(&c.lock)
		return true
	}

	if !block {
		unlock(&c.lock)
		return false
	}

	// 通道上的块。一些接收者会帮我们完成操作。
	gp := getg()
	mysg := acquireSudog()
	mysg.releasetime = 0
	if t0 != 0 {
		mysg.releasetime = -1
	}
	// 赋值元素和排队mysg 
	// 在gp.com上等待copystack可以找到它的位置。
	mysg.elem = ep
	mysg.waitlink = nil
	mysg.g = gp
	mysg.isSelect = false
	mysg.c = c
	gp.waiting = mysg
	gp.param = nil
	c.sendq.enqueue(mysg)
	// 向任何试图缩小我们堆栈的人发出信号，表示我们将把
	// 停在频道上。此G的状态
	// 更改与设置gp.activeStackChans之间的窗口对于
	// 堆栈收缩不安全。
	atomic.Store8(&gp.parkingOnChan, 1)
	gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)
	// 确保发送的值保持活动状态，直到接收者将其复制出去。sudog有一个指向
	// 堆栈对象的指针，但sudog不被视为
	// 堆栈跟踪程序的根。有人把我们吵醒了。
	KeepAlive(ep)

	if mysg != gp.waiting {
		throw("G waiting list is corrupted")
	}
	gp.waiting = nil
	gp.activeStackChans = false
	closed := !mysg.success
	gp.param = nil
	if mysg.releasetime > 0 {
		blockevent(mysg.releasetime-t0, 2)
	}
	mysg.c = nil
	releaseSudog(mysg)
	if closed {
		if c.closed == 0 {
			throw("chansend: spurious wakeup")
		}
		panic(plainError("send on closed channel"))
	}
	return true
}

// 发送在空通道c上处理发送操作。
// 发送方发送的值ep复制到接收方sg。
// 然后，接收器被唤醒，开始它的快乐之路。
// 通道c必须为空且已锁定。用解锁F发送解锁c。
// sg必须已从c中退出队列。
// ep必须为非nil并指向堆或调用方的堆栈。假装我们通过缓冲区，即使我们直接复制。请注意，我们需要仅在启用时增加
func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
	if raceenabled {
		if c.dataqsiz == 0 {
			racesync(c, sg)
		} else {
			// 头部/尾部位置。
			racenotify(c, c.recvx, nil)
			racenotify(c, c.recvx, sg)
			c.recvx++
			if c.recvx == c.dataqsiz {
				c.recvx = 0
			}
			c.sendx = c.recvx // c.sendx=（c.sendx+1）%c.dataqsiz 
		}
	}
	if sg.elem != nil {
		sendDirect(c.elemtype, sg, ep)
		sg.elem = nil
	}
	gp := sg.g
	unlockf()
	gp.param = unsafe.Pointer(sg)
	sg.success = true
	if sg.releasetime != 0 {
		sg.releasetime = cputicks()
	}
	goready(gp, skip+1)
}

// 在无缓冲或空缓冲通道上发送和接收的是
// 仅当一个运行goroutine的操作写入另一个运行goroutine的堆栈时。GC假设在goroutine运行时堆栈只写
// 并且只由该
// goroutine完成。使用写屏障足以弥补
// 违反该假设，但写屏障必须起作用。
// typedmemmove将调用bulkBarrierPreWrite，但目标字节
// 不在堆中，因此这没有帮助。我们安排打电话给
// memmove，改为键入BITSBULKBARRIER。

func sendDirect(t *_type, sg *sudog, src unsafe.Pointer) {
	// src在我们的堆栈上，dst是另一个堆栈上的插槽。

	// 一旦我们从sg中读取sg.elem，如果目标堆栈被复制（收缩），它将不再更新。
	// 所以请确保在读取和使用之间不会发生抢占点。
	dst := sg.elem
	typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.size)
	// 不需要cgo写屏障检查，因为dst始终是
	// Go内存。
	memmove(dst, src, t.size)
}

func recvDirect(t *_type, sg *sudog, dst unsafe.Pointer) {
	// dst在我们的堆栈或堆上，src在另一个堆栈上。
	// 通道已锁定，因此src在此
	// 操作期间不会移动。
	src := sg.elem
	typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.size)
	memmove(dst, src, t.size)
}

func closechan(c *hchan) {
	if c == nil {
		panic(plainError("close of nil channel"))
	}

	lock(&c.lock)
	if c.closed != 0 {
		unlock(&c.lock)
		panic(plainError("close of closed channel"))
	}

	if raceenabled {
		callerpc := getcallerpc()
		racewritepc(c.raceaddr(), callerpc, funcPC(closechan))
		racerelease(c.raceaddr())
	}

	c.closed = 1

	var glist gList

	// 释放所有读者
	for {
		sg := c.recvq.dequeue()
		if sg == nil {
			break
		}
		if sg.elem != nil {
			typedmemclr(c.elemtype, sg.elem)
			sg.elem = nil
		}
		if sg.releasetime != 0 {
			sg.releasetime = cputicks()
		}
		gp := sg.g
		gp.param = unsafe.Pointer(sg)
		sg.success = false
		if raceenabled {
			raceacquireg(gp, c.raceaddr())
		}
		glist.push(gp)
	}

	// 释放所有编剧（他们会惊慌）
	for {
		sg := c.sendq.dequeue()
		if sg == nil {
			break
		}
		sg.elem = nil
		if sg.releasetime != 0 {
			sg.releasetime = cputicks()
		}
		gp := sg.g
		gp.param = unsafe.Pointer(sg)
		sg.success = false
		if raceenabled {
			raceacquireg(gp, c.raceaddr())
		}
		glist.push(gp)
	}
	unlock(&c.lock)

	// 现在我们已解除频道锁定，准备好所有Gs。
	for !glist.empty() {
		gp := glist.pop()
		gp.schedlink = 0
		goready(gp, 3)
	}
}

// empty报告从c读取是否会阻塞（即通道为
// empty）。它使用可变状态的单个原子读取。
func empty(c *hchan) bool {
	// c.dataqsiz是不可变的。
	if c.dataqsiz == 0 {
		return atomic.Loadp(unsafe.Pointer(&c.sendq.first)) == nil
	}
	return atomic.Loaduint(&c.qcount) == 0
}

// 编译代码
// go:nosplit 
func chanrecv1(c *hchan, elem unsafe.Pointer) {
	chanrecv(c, elem, true)
}

// go:nosplit 
func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) {
	_, received = chanrecv(c, elem, true)
	return
}

// chanrecv在通道c上接收并将接收到的数据写入ep。
// ep可能为零，在这种情况下，接收到的数据将被忽略。
// 如果block==false且没有可用元素，则返回（false，false）。
// 否则，如果c是闭合的，则将*ep置零并返回（true，false）。
// 否则，用元素填充*ep并返回（true，true）。
// 非nil ep必须指向堆或调用方的堆栈。
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
	// raceabled:不需要检查ep，因为它总是在堆栈上
	// 或者是由reflect分配的新内存。

	if debugChan {
		print("chanrecv: chan=", c, "\n")
	}

	if c == nil {
		if !block {
			return
		}
		gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)
		throw("unreachable")
	}

	// 快速路径：在未获取锁的情况下检查非阻塞操作是否失败。
	if !block && empty(c) {
		// 在观察到信道未准备好接收后，我们观察
		// 信道是否关闭。
		// 
		// 重新排序这些检查可能会导致在接近终点时出现不正确的行为。
		// 例如，如果通道是打开的，而不是空的，则通道关闭，然后排空，
		// 重新排序的读取可能会错误地指示“打开且为空”。为了防止重新排序，
		// 我们对这两种检查都使用原子负载，并依赖于在
		// 中发生的清空和关闭操作，在同一个锁下分离关键部分。当关闭
		// /带有阻止发送的无缓冲通道时，此假设失败，但无论如何，这是一种错误情况。
		if atomic.Load(&c.closed) == 0 {
			// 由于某个频道无法重新打开，后续观察到该频道
			// 未关闭意味着该频道在
			// 第一次观察。我们的行为就好像我们当时观察到了频道
			// 并报告接收无法继续。
			return
		}
		// 通道不可逆地关闭。重新检查通道是否有任何待接收的数据
		// 可能在上述空检查和关闭检查之间到达。
		// 在进行此类发送时，这里还需要顺序一致性。
		if empty(c) {
			// 通道不可逆地关闭且为空。
			if raceenabled {
				raceacquire(c.raceaddr())
			}
			if ep != nil {
				typedmemclr(c.elemtype, ep)
			}
			return true, false
		}
	}

	var t0 int64
	if blockprofilerate > 0 {
		t0 = cputicks()
	}

	lock(&c.lock)

	if c.closed != 0 && c.qcount == 0 {
		if raceenabled {
			raceacquire(c.raceaddr())
		}
		unlock(&c.lock)
		if ep != nil {
			typedmemclr(c.elemtype, ep)
		}
		return true, false
	}

	if sg := c.sendq.dequeue(); sg != nil {
		// 找到正在等待的发件人。如果缓冲区大小为0，则直接从发送方接收值
		// 。否则，从队列头
		// 接收，并将发送方的值添加到队列尾（两者都映射到
		// 相同的缓冲槽，因为队列已满）。
		recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
		return true, true
	}

	if c.qcount > 0 {
		// 直接从队列接收
		qp := chanbuf(c, c.recvx)
		if raceenabled {
			racenotify(c, c.recvx, nil)
		}
		if ep != nil {
			typedmemmove(c.elemtype, ep, qp)
		}
		typedmemclr(c.elemtype, qp)
		c.recvx++
		if c.recvx == c.dataqsiz {
			c.recvx = 0
		}
		c.qcount--
		unlock(&c.lock)
		return true, true
	}

	if !block {
		unlock(&c.lock)
		return false, false
	}

	// 没有可用的发送方：此通道上的块。
	gp := getg()
	mysg := acquireSudog()
	mysg.releasetime = 0
	if t0 != 0 {
		mysg.releasetime = -1
	}
	// 在gp.waiting copystack可以找到mysg 
	// 上分配元素和排队之间没有堆栈拆分。
	mysg.elem = ep
	mysg.waitlink = nil
	gp.waiting = mysg
	mysg.g = gp
	mysg.isSelect = false
	mysg.c = c
	gp.param = nil
	c.recvq.enqueue(mysg)
	// 向任何试图缩小我们堆栈的人发出信号，表示我们将把它停在频道上。此G的状态
	// 更改与设置gp.activeStackChans之间的窗口对于
	// 堆栈收缩不安全。
	atomic.Store8(&gp.parkingOnChan, 1)
	gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanReceive, traceEvGoBlockRecv, 2)

	// 有人把我们吵醒了
	if mysg != gp.waiting {
		throw("G waiting list is corrupted")
	}
	gp.waiting = nil
	gp.activeStackChans = false
	if mysg.releasetime > 0 {
		blockevent(mysg.releasetime-t0, 2)
	}
	success := mysg.success
	gp.param = nil
	mysg.c = nil
	releaseSudog(mysg)
	return true, success
}

// recv在全通道c上处理接收操作。
// 共有两部分：
// 1）发送方sg发送的值被放入信道
// 并且发送方被唤醒，开始愉快的旅程。
// 2）接收器接收到的值（当前G）是写入ep的
// 。
// 对于同步通道，两个值相同。
// 对于异步信道，接收方从
// 信道缓冲区获取其数据，发送方的数据放入
// 信道缓冲区。
// 通道c必须已满且已锁定。recv用unlockf解锁c。
// sg必须已从c中退出队列。
// 非nil ep必须指向堆或调用方的堆栈。
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
	if c.dataqsiz == 0 {
		if raceenabled {
			racesync(c, sg)
		}
		if ep != nil {
			// 从发送方复制数据
			recvDirect(c.elemtype, sg, ep)
		}
	} else {
		// 队列已满。在
		// 队列的最前面取项目。将发送方放入队列
		// 其项目位于队列尾部。由于
		// 队列已满，因此它们都是同一个插槽。
		qp := chanbuf(c, c.recvx)
		if raceenabled {
			racenotify(c, c.recvx, nil)
			racenotify(c, c.recvx, sg)
		}
		// 将数据从队列复制到接收方
		if ep != nil {
			typedmemmove(c.elemtype, ep, qp)
		}
		// 将数据从发送方复制到队列
		typedmemmove(c.elemtype, qp, sg.elem)
		c.recvx++
		if c.recvx == c.dataqsiz {
			c.recvx = 0
		}
		c.sendx = c.recvx // c.sendx=（c.sendx+1）%c.dataqsiz 
	}
	sg.elem = nil
	gp := sg.g
	unlockf()
	gp.param = unsafe.Pointer(sg)
	sg.success = true
	if sg.releasetime != 0 {
		sg.releasetime = cputicks()
	}
	goready(gp, skip+1)
}

func chanparkcommit(gp *g, chanLock unsafe.Pointer) bool {
	// 存在指向gp堆栈的未锁定sudog。Stack 
	// 复制必须锁定那些sudog的通道。
	// 在此处设置activeStackChans，而不是在尝试停止
	// 之前，因为我们可能会在
	// 通道锁上的堆栈增长中自锁死。
	gp.activeStackChans = true
	// 标记现在进行堆栈收缩是安全的，
	// 因为任何获取此G堆栈进行收缩的线程都保证在此存储之后观察activeStackChans。
	atomic.Store8(&gp.parkingOnChan, 0)
	// 请确保在设置activeStackChans和
	// 取消驻店后解锁。在我们解锁chanLock 
	// 的那一刻，我们冒着gp被通道操作和
	// 准备就绪的风险，因此gp可以在
	// 解锁之前继续运行（甚至对gp本身）。
	unlock((*mutex)(chanLock))
	return true
}

// 编译器实现
// 
// 选择{
// 案例c<-v:
// /..foo 
// 默认值：
// /..bar 
// /}
// /..foo 
// /}其他{
// /..bar 
// /}
// /
func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) {
	return chansend(c, elem, false, getcallerpc())
}

// 编译器实现
// 
// 选择{
// /案例五，ok=<-c:
// /..foo 
// 默认值：
// /..bar 
// /}
// /
// 作为
// /..
// 如果选中，ok=selectnbrecv（&v，c）；选中{
// /{
// /..bar 
// /}简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯737简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简讯简。chancap 
func selectnbrecv(elem unsafe.Pointer, c *hchan) (selected, received bool) {
	return chanrecv(c, elem, false)
}

func reflect_chansend(c *hchan, elem unsafe.Pointer, nb bool) (selected bool) {
	return chansend(c, elem, !nb, getcallerpc())
}

func reflect_chanrecv(c *hchan, nb bool, elem unsafe.Pointer) (selected bool, received bool) {
	return chanrecv(c, elem, !nb)
}

func reflect_chanlen(c *hchan) int {
	if c == nil {
		return 0
	}
	return int(c.qcount)
}

func reflectlite_chanlen(c *hchan) int {
	if c == nil {
		return 0
	}
	return int(c.qcount)
}

func reflect_chancap(c *hchan) int {
	if c == nil {
		return 0
	}
	return int(c.dataqsiz)
}

// go:linkname reflect\u chanclose reflect.chanclose 
func reflect_chanclose(c *hchan) {
	closechan(c)
}

func (q *waitq) enqueue(sgp *sudog) {
	sgp.next = nil
	x := q.last
	if x == nil {
		sgp.prev = nil
		q.first = sgp
		q.last = sgp
		return
	}
	sgp.prev = x
	x.next = sgp
	q.last = sgp
}

func (q *waitq) dequeue() *sudog {
	for {
		sgp := q.first
		if sgp == nil {
			return nil
		}
		y := sgp.next
		if y == nil {
			q.first = nil
			q.last = nil
		} else {
			y.prev = nil
			q.first = y
			sgp.next = nil // 标记为已删除（请参阅出列sudog）
		}

		// 如果由于
		// 选择而将goroutine放入此队列，在goroutine 
		// 被另一个案例唤醒和它抓取
		// 通道锁之间有一个小窗口。一旦它有了锁
		// 它就会从队列中移除自己，所以我们在那之后就看不到它了。
		// 我们在G结构中使用一个标志来告诉我们何时有人
		// 其他人赢得了向此goroutine发送信号的比赛，但goroutine 
		// 尚未从队列中移除自己。
		if sgp.isSelect && !atomic.Cas(&sgp.g.selectDone, 0, 1) {
			continue
		}

		return sgp
	}
}

func (c *hchan) raceaddr() unsafe.Pointer {
	// 处理指向
	// 的通道上的类读和类写操作。这些操作发生在该地址。避免使用qcount 
	// 或dataqsiz的地址，因为len（）和cap（）内置项读取这些地址，我们不希望它们与
	// 之类的操作竞争。
	return unsafe.Pointer(&c.buf)
}

func racesync(c *hchan, sg *sudog) {
	racerelease(chanbuf(c, 0))
	raceacquireg(sg.g, chanbuf(c, 0))
	racereleaseg(sg.g, chanbuf(c, 0))
	raceacquire(chanbuf(c, 0))
}

// 将涉及缓冲区条目idx的发送或接收通知竞赛检测器
// 和通道c或其通信伙伴sg。
// 此函数处理c.elemsize==0的特殊情况。
func racenotify(c *hchan, idx uint, sg *sudog) {
	// 我们本可以传递与条目idx 
	// 相对应的不安全的.指针，而不是idx本身。但是，在这个函数的未来版本中，我们可以使用idx更好地处理elemsize==0的情况。
	// 检测器的未来改进是使用c和idx调用TSan:
	// 这样，Go将继续不为elemsize==0的通道
	// 分配缓冲区条目，但竞争检测器可以处理引擎盖下的多个
	// 同步对象（每个idx一个同步对象）
	qp := chanbuf(c, idx)
	// 当elemsize==0时，我们不会为通道分配一个完整的缓冲区。
	// 竞赛检测器使用
	// c.buf作为唯一的缓冲区项，而不是单个缓冲区项。这种简化防止我们遵循内存模型的规则（规则为
	// 在RaceReleaseAccquire中实现）。相反，我们在与c.buf.
	// 之前的信息
	if c.elemsize == 0 {
		if sg == nil {
			raceacquire(qp)
			racerelease(qp)
		} else {
			raceacquireg(sg.g, qp)
			racereleaseg(sg.g, qp)
		}
	} else {
		if sg == nil {
			racereleaseacquire(qp)
		} else {
			racereleaseacquireg(sg.g, qp)
		}
	}
}
